package room

import (
	"context"
	"encoding/json"
	"strings"
	"time"

	"code.byted.org/bytertc/whiteboard_server/biz/api"
	"code.byted.org/bytertc/whiteboard_server/biz/dal"
	"code.byted.org/bytertc/whiteboard_server/biz/dal/spec"
	"code.byted.org/bytertc/whiteboard_server/biz/pkg/biz_error"
	"code.byted.org/bytertc/whiteboard_server/biz/pkg/mq"
	"code.byted.org/gopkg/ctxvalues"
	"code.byted.org/gopkg/logs/v2/log"
	"github.com/pkg/errors"
)

type Room interface {
	Enter(ctx context.Context, unique spec.OperatorUnique) error
	Quit(ctx context.Context, unique spec.OperatorUnique) error
	HandleException(ctx context.Context) error
	ClearInvalid(ctx context.Context) error
}

func NewRoom(topic string, sender mq.KafkaSender, repo dal.Repo) Room {
	return &room{
		topic:       topic,
		kafkaSender: sender,
		repo:        repo,
	}
}

type room struct {
	topic       string
	repo        dal.Repo
	kafkaSender mq.KafkaSender
}

func (r *room) Enter(ctx context.Context, unique spec.OperatorUnique) error {
	lastEnterTime, err := r.repo.GetEnterTimeStamp(ctx, unique)
	if err != nil {
		return biz_error.UnknownError.Wrap(errors.WithStack(err))
	}
	//将上次进房时长上报
	if lastEnterTime > 0 {
		quitTime, err := r.repo.GetHeartbeatTimeStamp(ctx, unique)
		if err != nil {
			return biz_error.UnknownError.Wrap(errors.WithStack(err))
		}
		if quitTime == 0 {
			log.V1.CtxWarn(ctx, "GetHeartbeatTimeStamp not exist app:%s,room:%s,operator:%s", unique.GetAppId(), unique.GetRoomId(), unique.GetOperator())
		} else if quitTime > 0 && quitTime < lastEnterTime {
			log.V1.CtxWarn(ctx, "GetHeartbeatTimeStamp unexpected app:%s,room:%s,operator:%s,quitTime:%d", unique.GetAppId(), unique.GetRoomId(), unique.GetOperator(), quitTime)
		} else {
			err := r.pushRecord2Bill(ctx, unique, lastEnterTime, quitTime)
			if err != nil {
				return biz_error.UnknownError.Wrap(errors.WithStack(err))
			}
		}
	}
	err = r.repo.SetEnterTimeStamp(ctx, unique, time.Now().UnixNano()/1e6)
	if err != nil {
		return biz_error.UnknownError.Wrap(errors.WithStack(err))
	}
	r.repo.SetRoomSet(ctx, unique)
	return nil
}
func (r *room) Quit(ctx context.Context, unique spec.OperatorUnique) error {
	lastEnterTime, err := r.repo.GetEnterTimeStamp(ctx, unique)
	if err != nil {
		return biz_error.UnknownError.Wrap(errors.WithStack(err))
	}
	if lastEnterTime <= 0 {
		log.V1.CtxError(ctx, "not find enter timestamp app:%s,room:%s,operator:%s", unique.GetAppId(), unique.GetRoomId(), unique.GetOperator())
		return nil
	}
	err = r.pushRecord2Bill(ctx, unique, lastEnterTime, time.Now().UnixNano()/1e6)
	if err != nil {
		return biz_error.UnknownError.Wrap(errors.WithStack(err))
	}
	err = r.repo.DelEnterTimeStamp(ctx, unique)
	if err != nil {
		log.V1.CtxError(ctx, "DelEnterTimeStamp fail app:%s,room:%s,operator:%s,err:%+v", unique.GetAppId(), unique.GetRoomId(), unique.GetOperator(), err)
	}
	err = r.repo.DelHeartbeatTimeStamp(ctx, unique)
	if err != nil {
		log.V1.CtxError(ctx, "DelHeartbeatTimeStamp fail app:%s,room:%s,operator:%s,err:%+v", unique.GetAppId(), unique.GetRoomId(), unique.GetOperator(), err)
	}
	return nil
}
func (r *room) HandleException(ctx context.Context) error {
	roomSet, err := r.repo.GetTopNRooms(ctx, 5)
	if err != nil {
		log.V1.CtxError(ctx, "HandleException fail err:%+v", err)
		return nil
	}
	for _, member := range roomSet {
		eles := strings.Split(member, dal.SplitCharacters)
		if len(eles) != 2 {
			log.V1.CtxError(ctx, "roomSet member not contain RoomSplitCharacter member:%s", member)
			continue
		}
		appId := eles[0]
		roomId := eles[1]
		uniq := &api.EnterRoomRequest{
			AppID:  appId,
			RoomID: roomId,
		}
		//五分钟前打点
		deadline := time.Now().UnixNano()/1e6 - 5*60*1000
		heartbeatStamps, err := r.repo.GetHeartbeatTimeStamps(ctx, uniq, deadline)
		if err != nil {
			log.V1.CtxError(ctx, "GetHeartbeatTimeStamps fail,app:%s,room:%s,err:%+v", uniq.AppID, uniq.RoomID, err)
			continue
		}
		for operator, quitTime := range heartbeatStamps {
			uniq.Operator = operator
			enterTime, err := r.repo.GetEnterTimeStamp(ctx, uniq)
			if err != nil {
				log.V1.CtxError(ctx, "GetEnterTimeStamp fail,app:%s,room:%s,operator:%s,err:%+v", uniq.AppID, uniq.RoomID, uniq.Operator, err)
				continue
			}
			if enterTime <= 0 {
				log.V1.CtxError(ctx, "not find GetEnterTimeStamp timestamp app:%s,room:%s,operator:%s", uniq.AppID, uniq.RoomID, uniq.Operator)
				continue
			}

			if enterTime < quitTime {
				err := r.pushRecord2Bill(ctx, uniq, enterTime, quitTime)
				if err != nil {
					continue
				}
			}
			r.repo.DelEnterTimeStamp(ctx, uniq)
			r.repo.DelHeartbeatTimeStamp(ctx, uniq)

		}
		//更新一下，扔到最后一个，防止下次再被遍历到
		r.repo.UpdateRoomSet(ctx, uniq)
	}

	return nil
}

func (r *room) ClearInvalid(ctx context.Context) error {
	roomSet, err := r.repo.GetTopNRooms(ctx, 100)
	if err != nil {
		log.V1.CtxError(ctx, "HandleException fail err:%+v", err)
		return nil
	}
	for _, member := range roomSet {
		eles := strings.Split(member, dal.SplitCharacters)
		if len(eles) != 2 {
			log.V1.CtxError(ctx, "roomSet member not contain RoomSplitCharacter member:%s", member)
			continue
		}
		appId := eles[0]
		roomId := eles[1]
		uniq := &api.EnterRoomRequest{
			AppID:  appId,
			RoomID: roomId,
		}
		size, err := r.repo.GetEnterSetSize(ctx, uniq)
		if err != nil {
			log.V1.CtxError(ctx, "GetEnterSetSize err:%s,member:%s", err, member)
			continue
		}
		if size == 0 {
			r.repo.DelRoom(ctx, uniq)
		}
	}
	return nil
}

func (r *room) pushRecord2Bill(ctx context.Context, unique spec.OperatorUnique, enterTime, quitTime int64) error {
	duration := quitTime - enterTime
	msgId := ctxvalues.LogIDDefault(ctx)
	msg := mq.DurationBillMsg{
		Type:      "whiteboard",
		MsgId:     msgId,
		Timestamp: time.Now().UnixNano() / 1e6,
		Data: mq.DurationData{
			UUID:      msgId,
			AppId:     unique.GetAppId(),
			RoomId:    unique.GetRoomId(),
			UserId:    unique.GetOperator(),
			RtmUserId: unique.GetOperator(),
			EnterTime: enterTime,
			Duration:  duration,
		},
	}
	if enterTime <= 0 {
		log.V1.CtxWarn(ctx, "enterTime invalid,msg:%+v", msg)
		return nil
	}
	if duration <= 0 {
		log.V1.CtxWarn(ctx, "quitTime < enterTime,msg:%+v", msg)
		return nil
	}
	data, err := json.Marshal(msg)
	if err != nil {
		return biz_error.EncodeError.Wrap(errors.Errorf("json marshal fail msg:%+v", msg))
	}
	err = r.kafkaSender.SendMessage(ctx, r.topic, string(data))
	if err != nil {
		//todo:异常处理
		log.V1.CtxError(ctx, "send kafka fail err:%+v,data:%s", err, data)
	}
	return nil
}
